package odfconverter;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

import javax.sound.sampled.AudioFormat.Encoding;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

enum ProcessingState {
	// Not archiving
	None,
	// Waiting for an entry
	EntryWaiting,
	// Processing an entry
	EntryStarted
}

public class ZipArchiveWriter extends XmlWriter {

	private class PZIP {
		public static final String Namespace = "urn:cleverage:xmlns:post-processings:zip";
		public static final String Prefix = "pzip";

		public static final String EntryElement = "entry";
		public static final String ArchiveElement = "archive";
		public static final String CopyElement = "copy";
		public static final String ExtractElement = "extract";
		public static final String ImportElement = "import";

		public static final String SourceAttr = "source";
		public static final String TargetAttr = "target";
		public static final String ContentTypeAttr = "content-type";
		public static final String ContentAttr = "content";
		public static final String CompressionAttr = "compression";
	}

	// / <summary>
	// / The zip archive
	// / </summary>
	// private ZipWriter _zipOutputStream;
	private ZipOutputStream _zipOutputStream;
	private ProcessingState _processingState = ProcessingState.None;
	private Stack<Element> _elements;

	private Element _currentPzipElement;
	private Node _currentPzipAttribute;
	private Node _currentNotPzipAttribute;

	// / <summary>
	// / A delegate <c>XmlWriter</c> that actually feeds the zip output stream.
	// / </summary>
	private XMLStreamWriter _delegateWriter = null;
	// / <summary>
	// / The delegate settings
	// / </summary>
	// --private XmlWriterSettings _delegateSettings = null;
	private ZipURIResolver _resolver;

	// / <summary>
	// / Table of binary files to be copied from the source to the target
	// package
	// / </summary>
	private Map<String, String> _binaries;

	// / <summary>
	// / A list of files to be extracted from the source package
	// / </summary>
	private Map<String, String> _extractFileList;

	// / <summary>
	// / A list of files to be imported from teh file system to the target
	// package
	// / </summary>
	private Map<String, String> _importFileList;

	private String _outputFile;

	private class ImageValue {
		public int Width;
		public int Height;
		public float VerticalResolution;
		public float HorizontalResolution;
	}

	private Map<String, ImageValue> _imageValues = new HashMap<String, ImageValue>();

	// private Dictionary<string, ImageValue> _imageValues = new
	// System.Collections.Generic.Dictionary<string, ImageValue>();
	public ZipArchiveWriter(ZipURIResolver zipUIRResolver)
	{
		_elements = new Stack<Element>();
		_resolver = zipUIRResolver;
		/*
            _delegateSettings = new XmlWriterSettings();
            _delegateSettings.OmitXmlDeclaration = false;
            _delegateSettings.CloseOutput = false;
            _delegateSettings.Encoding = Encoding.UTF8;
            _delegateSettings.Indent = false;
            // We have to use a new delegate per entry in the archive, 
            // so that XML conformance will be checked at the document level.
            //
            // It is not possible to check XML conformance at the document level with a single delegate 
            // that writes all the entries. We would have to use ConformanceLevel.Fragment then 
            // which would lead to a missing XML declaration.
            _delegateSettings.ConformanceLevel = ConformanceLevel.Document;

            _resolver = xmlResolver;
		 */
	}

	private void processPackageInstructions(Element element)
			throws XMLStreamException, IOException {
		String cpeName = _currentPzipElement.get_name();
		if (cpeName.equals(PZIP.ArchiveElement)) {
			// Prevent nested archive creation
			if (_processingState == ProcessingState.None) {
				if (_currentPzipElement.getAttributes().containsKey(
						PZIP.TargetAttr)) {
					// Debug.WriteLine("Creating archive : " +
					// _currentPzipElement.Attributes[PZIP.TargetAttr].Value);
					_zipOutputStream = new ZipOutputStream(new FileOutputStream(_currentPzipElement.getAttributes().get(PZIP.TargetAttr).get_value()));
					_processingState = ProcessingState.EntryWaiting;

					_binaries = new HashMap<String, String>();
					_extractFileList = new HashMap<String, String>();
					_importFileList = new HashMap<String, String>();
				} else {
					// TODO throw exception
				}
			}
		} else if (cpeName.equals(PZIP.EntryElement)) {
			// Prevent nested entry creation
			if (_processingState == ProcessingState.EntryWaiting) {
				if (_currentPzipElement.getAttributes().containsKey(PZIP.TargetAttr)) {
					//Debug.WriteLine("creating new part : " + _currentPzipElement.Attributes[PZIP.TargetAttr].Value);
                    //CompressionMethod compressionMethod = CompressionMethod.Deflated;
                    //if (_currentPzipElement.Attributes.ContainsKey(PZIP.CompressionAttr) && _currentPzipElement.Attributes[PZIP.CompressionAttr].Value.Equals("none"))
                    //{
                    //    compressionMethod = CompressionMethod.Stored;
                    //}
                    _zipOutputStream.putNextEntry(new ZipEntry(_currentPzipElement.getAttributes().get("target").get_value()));

                    if (_currentPzipElement.getAttributes().containsKey(PZIP.ContentTypeAttr) 
                    	&& _currentPzipElement.getAttributes().get(PZIP.ContentTypeAttr).get_value().equals("text/plain")
                        && _currentPzipElement.getAttributes().containsKey(PZIP.ContentAttr))
                    {
                        //Encoding enc = new System.Text.ASCIIEncoding();
                        byte[] buffer = _currentPzipElement.getAttributes().get(PZIP.ContentAttr).get_value().getBytes("ASCII");
                        _zipOutputStream.write(buffer, 0, buffer.length);
                    }
                    else
                    {
					XMLOutputFactory xof = XMLOutputFactory.newInstance();
					XMLStreamWriter xtw = null;
					_delegateWriter = xof.createXMLStreamWriter(_zipOutputStream,"UTF-8");
					//_delegateWriter = xof.createXMLStreamWriter(new FileWriter(_currentPzipElement.getAttributes().get("target").get_value()));
					_delegateWriter.writeStartDocument("UTF-8", "1.0");

					// _delegateWriter = XmlWriter.Create(_zipOutputStream,
					// _delegateSettings);
					// _delegateWriter.WriteStartDocument();

					_processingState = ProcessingState.EntryStarted;
					}
				}
			}
		} else if (cpeName.equals(PZIP.CopyElement)) {
			if (_processingState != ProcessingState.None) {
				if (_currentPzipElement.getAttributes().containsKey(
						PZIP.SourceAttr)
						&& _currentPzipElement.getAttributes().containsKey(
								PZIP.TargetAttr)) {
					String source = _currentPzipElement.getAttributes().get(
							PZIP.SourceAttr).get_value();
					String target = _currentPzipElement.getAttributes().get(
							PZIP.TargetAttr).get_value();

					// Debug.WriteLine(string.Format("copy source={0} target={1}",
					// source, target));

					if (_binaries != null && !_binaries.containsKey(target)) {
						// target is the key because there are cases where one
						// file has to be
						// copied twice to different locations
						_binaries.put(target, source);
					}
				}
			}
		} else if (cpeName.equals(PZIP.ExtractElement)) {
			if (_processingState != ProcessingState.None) {
				if (_currentPzipElement.getAttributes().containsKey(
						PZIP.SourceAttr)
						&& _currentPzipElement.getAttributes().containsKey(
								PZIP.TargetAttr)) {
					String source = _currentPzipElement.getAttributes().get(
							PZIP.SourceAttr).get_value();
					String target = _currentPzipElement.getAttributes().get(
							PZIP.TargetAttr).get_value();

					// Debug.WriteLine(string.Format("copy source={0} target={1}",
					// source, target));

					if (_extractFileList != null
							&& !_extractFileList.containsKey(target)) {
						// target is the key because there are cases where one
						// file has to be
						// copied twice to different locations
						_extractFileList.put(target, source);
					}
				}
			}
		} else if (cpeName.equals(PZIP.ImportElement)) {
			if (_processingState != ProcessingState.None) {
				if (_currentPzipElement.getAttributes().containsKey(
						PZIP.SourceAttr)
						&& _currentPzipElement.getAttributes().containsKey(
								PZIP.TargetAttr)) {
					String source = _currentPzipElement.getAttributes().get(
							PZIP.SourceAttr).get_value();
					String target = _currentPzipElement.getAttributes().get(
							PZIP.TargetAttr).get_value();

					// Debug.WriteLine(string.Format("copy source={0} target={1}",
					// source, target));

					if (_importFileList != null
							&& !_importFileList.containsKey(target)) {
						// target is the key because there are cases where one
						// file has to be
						// copied twice to different locations
						_importFileList.put(target, source);
					}
				}
			}
		}
	}

	@Override
	public void WriteEndAttribute() {
		// super.WriteEndAttribute();
		// System.out.println("WriteEndAttribute");
		Node elt = _elements.peek();
		// Debug.WriteLine("[WriteEndAttribute] element=" + elt.Name);

		if (!elt.get_namespace().equals(PZIP.Namespace)) {
			if (_delegateWriter != null) {
				// --_delegateWriter.WriteEndAttribute();
				if (_currentNotPzipAttribute != null) {
					try {
						if ((bIsNull(_currentNotPzipAttribute.get_prefix()) && bIsNull(_currentNotPzipAttribute.get_namespace()))
							|| (bIsNull(_currentNotPzipAttribute.get_prefix()) && _currentNotPzipAttribute.get_namespace().endsWith("http://www.w3.org/2000/xmlns/"))) 
						{
							_delegateWriter.writeAttribute(
									_currentNotPzipAttribute.get_name(),
									_currentNotPzipAttribute.get_value());
						}  else if (!bIsNull(_currentNotPzipAttribute.get_prefix()) && !bIsNull(_currentNotPzipAttribute.get_namespace())) {
							_delegateWriter.writeAttribute(
									_currentNotPzipAttribute.get_prefix(),
									_currentNotPzipAttribute.get_namespace(),
									_currentNotPzipAttribute.get_name(),
									_currentNotPzipAttribute.get_value());
						} else {
							System.err.print(_currentNotPzipAttribute.get_prefix()
									+ ":" + _currentNotPzipAttribute.get_namespace()
									+ ":" + _currentNotPzipAttribute.get_name()
									+ ":" + _currentNotPzipAttribute.get_value());
						}
					} catch (XMLStreamException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					_currentNotPzipAttribute = null;
				}
			}
		} else {
			_currentPzipAttribute = null;
		}
	}

	@Override
	public void WriteEndElement() {
		// super.WriteEndElement();
		// System.out.println("WriteEndElement");
		// check for pending pzip instructions
		if (_currentPzipElement != null
				&& PZIP.Namespace.equals(_currentPzipElement.get_namespace())) {
			int i = 0;
			try {
				processPackageInstructions(_currentPzipElement);
			} catch (XMLStreamException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			_currentPzipElement = null;
		}

		Node elt = _elements.pop();

		if (!elt.get_namespace().equals(PZIP.Namespace)) {
			// Debug.WriteLine("delegate - </" + elt.Name + ">");
			if (_delegateWriter != null) {
				try {
					_delegateWriter.writeEndElement();
				} catch (XMLStreamException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		} else {
			if (elt.get_name().equals(PZIP.ArchiveElement)) {
				if (_processingState == ProcessingState.EntryWaiting) {
					if (_zipOutputStream != null) {
						try {
						// Copy binaries before closing the archive
						copyBinaries();
						// Added by Sonata - Copy Audio files before closing
						// archive
						extractFiles();
						importFiles();
						// Debug.WriteLine("[closing archive]");
						 							_zipOutputStream.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						 _zipOutputStream = null;
					}
					_processingState = ProcessingState.None;
				}
			} else if (elt.get_name().equals(PZIP.EntryElement)) {
				if (_processingState == ProcessingState.EntryStarted) {
					if (_delegateWriter != null) {
						// Debug.WriteLine("[end part]");
						try {
							_delegateWriter.writeEndDocument();
							_delegateWriter.flush();
							_delegateWriter.close();
						} catch (XMLStreamException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

						_delegateWriter = null;
					}
					_processingState = ProcessingState.EntryWaiting;
				}
			}
		}
	}

	@Override
	public void WriteStartAttribute(String prefix, String localName, String ns) {
		// super.WriteStartAttribute(prefix, localName, ns);
		// System.out.println("WriteStartAttribute:"+prefix+"-"+localName+"-"+ns);
		Element elt = _elements.peek();
		// Debug.WriteLine("[WriteStartAttribute] prefix=" + prefix +
		// " localName=" + localName + " ns=" + ns + " element=" + elt.Name);

		if (!elt.get_namespace().equals(PZIP.Namespace)) {
			if (_delegateWriter != null) {
				// ---_delegateWriter.WriteStartAttribute(prefix, localName,
				// ns);
				_currentNotPzipAttribute = new Node(prefix, localName, ns);
			}
		} else if (ns.equals(PZIP.Namespace)) {
			// we only store attributes tied to zip processing instructions
			_currentPzipAttribute = new Node(prefix, localName, ns);
			elt.getAttributes().put(localName, _currentPzipAttribute);// 不知道是否getAttribute后能否保存到elt中
		}
	}

	@Override
	public void WriteStartElement(String prefix, String localName, String ns) {
		// super.WriteStartElement(prefix, localName, ns);
		// System.out.println("WriteStartElement:"+prefix+"-"+localName+"-"+ns);
		// check for pending pzip instructions
		if (_currentPzipElement != null
				&& PZIP.Namespace.equals(_currentPzipElement.get_namespace())) {
			try {
				processPackageInstructions(_currentPzipElement);
			} catch (XMLStreamException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			_currentPzipElement = null;
		}

		// Debug.WriteLine("[startElement] prefix=" + prefix + " localName=" +
		// localName + " ns=" + ns);
		Element newElement = new Element(prefix, localName, ns);
		_elements.push(newElement);

		// not a zip processing instruction
		if (!PZIP.Namespace.equals(ns)) {
			if (_delegateWriter != null) {
				// Debug.WriteLine("{WriteStartElement=" + localName +
				// "} delegate");
				try {
					_delegateWriter.writeStartElement(prefix, localName, ns);
				} catch (XMLStreamException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else {
				// Debug.WriteLine("[WriteStartElement=" + localName +
				// " } delegate is null");
			}
		} else {
			// remember the current pzip node
			_currentPzipElement = newElement;
		}
	}

	@Override
	public void WriteString(String text) {
		// super.WriteString(text);
		// System.out.println("WriteString:"+text);
		String strDefaultValue = "0";
		Element elt = _elements.peek();

		if (!elt.get_namespace().equals(PZIP.Namespace)) {
			if (_delegateWriter != null) {
				// ---_delegateWriter.WriteString(text);
				if (_currentNotPzipAttribute != null) {
					if (_currentNotPzipAttribute.get_value() != null) {
						_currentNotPzipAttribute
								.set_value(_currentNotPzipAttribute.get_value()
										+ text);
					} else {
						_currentNotPzipAttribute.set_value(text);
					}
				} else {
					try {
						_delegateWriter.writeCharacters(text);
					} catch (XMLStreamException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		} else {
			if (_currentPzipAttribute != null) {
				// store attribute value (concatenation is needed because
				// attribute value may consist of
				// more than one node when using XSL attribute value templates
				// (i.e. curly braces {})
				if (_currentPzipAttribute.get_value() != null) {
					_currentPzipAttribute.set_value(_currentPzipAttribute
							.get_value()
							+ text);
				} else {
					_currentPzipAttribute.set_value(text);
				}
				//
			}
		}
	}

	public Boolean bIsNull(String s)
	{
		if(s == null || s.equals(""))
		{
			return true;
		}
		return false;
	}

    /// <summary>
    /// Transfer binary files between two zip archives. 
    /// </summary>
    private void copyBinaries() throws IOException
    {
        for (String target : _binaries.keySet())
        {
            if (_zipOutputStream != null)
            {
                // we also add empty streams to the target package
                _zipOutputStream.putNextEntry(new ZipEntry(target));
                
                String source = _binaries.get(target);
                InputStream sourceStream = getStream(source);
                
                if (sourceStream != null)
                {
                    int bytesCopied = streamCopy(sourceStream, _zipOutputStream);
                    //Debug.WriteLine("CopyBinary : " + source + " --> " + target + ", bytes copied = " + bytesCopied);
                }
            }
        }
    }

    private final static int BUFFER_SIZE = 4096;

    private InputStream getStream(String source) throws FileNotFoundException
    {
        //get stream to embedded resource
    	//pzip:source="#CER#WordprocessingConverter.dll#OdfConverter.Wordprocessing.resources.OLEplaceholder.png#"
        if (source.startsWith("#CER#") && source.endsWith("#"))
        {
        	return new FileInputStream("f:\\work3\\FTEngine\\OLEplaceholder.png");
        }
        //get stream to file in zip
        //Uri absoluteUri = _resolver.ResolveUri(null, source);
        else{
            return _resolver.GetEntry(source);
        }
    }

    private int streamCopy(InputStream source, ZipOutputStream destination) throws IOException
    {
        if (source != null && destination != null)
        {
            byte[] data = new byte[BUFFER_SIZE];
            int read = -1;
            int bytesCopied = 0;
            while((read=source.read(data))!=-1)
            {
                if(read>0)
                {
                	bytesCopied += read;
                	destination.write(data, 0, read);
                }
            }   
            source.close();
            return bytesCopied;
        }
        return -1;
    }
    
	private void extractFiles() {

	}

	private void importFiles() {

	}

	// / <summary>
	// / Simple representation of elements or attributes nodes
	// / </summary>
	private class Node {
		private String _name;
		private String _prefix;
		private String _namespace;
		private String _value;

		public String get_name() {
			return _name;
		}

		public void set_name(String _name) {
			this._name = _name;
		}

		public String get_prefix() {
			return _prefix;
		}

		public void set_prefix(String _prefix) {
			this._prefix = _prefix;
		}

		public String get_namespace() {
			return _namespace;
		}

		public void set_namespace(String _namespace) {
			this._namespace = _namespace;
		}

		public String get_value() {
			return _value;
		}

		public void set_value(String _value) {
			this._value = _value;
		}

		public Node(String prefix, String name, String ns) {
			this._prefix = prefix;
			this._name = name;
			this._namespace = ns;
		}
	}

	private class Element extends Node {
		// private Dictionary<String, Node> _attributes;
		private Map<String, Node> _attributes;

		public Element(String prefix, String name, String ns) {
			super(prefix, name, ns);
		}

		public Map<String, Node> getAttributes() {
			// lazy initialization
			if (_attributes == null) {
				_attributes = new HashMap<String, Node>();
			}
			return _attributes;
		}
	}
}
